#include "stdafx.h"

#include <MyControls/SegmentList.h>
#include <MyControls/Timeline.h>
#include <MyFC/Utility.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

namespace My {

// SegmentList::Animator

void SegmentList::Animator::OnTimer()
{
	m_Owner.UpdateAnimation();
}

SegmentList::SegmentList(Timeline & t) : 
	m_Timeline(t),
	m_pSegments(0),
	m_Animator(* this)
{
	m_VOffset = -1;
	m_VRange = 0;
	m_VPage = 0;
	m_VAutoScroll = 0;

	m_InsertionVisible = false;
	m_ShowInsertion = false;

	m_Left.Load("SegmentLeft", "SegmentLeftMask");
	m_Right.Load("SegmentRight", "SegmentRightMask");
	m_Mask.Load("Mask");
	m_MaskPressed.Load("MaskPressed");
	m_MaskGrayed.Load("MaskGrayed");

	VERIFY (m_DarkOrangeRipple.LoadBitmap("DarkOrangeRipple"));
	VERIFY (m_LightOrangeRipple.LoadBitmap("LightOrangeRipple"));
	VERIFY (m_DarkBlueRipple.LoadBitmap("DarkBlueRipple"));
	VERIFY (m_LightBlueRipple.LoadBitmap("LightBlueRipple"));
	VERIFY (m_DarkGreenRipple.LoadBitmap("DarkGreenRipple"));
	VERIFY (m_LightGreenRipple.LoadBitmap("LightGreenRipple"));
	VERIFY (m_Alternate.LoadBitmap("Alternate"));

	m_Animator.SetDelay(200);

	m_Timeline.Add(this);

	m_AutoScroll.SetDelay(100);
	m_AutoScroll.Attach(this);

	m_Gap = 15;
}

SegmentList::~SegmentList()
{
	SetSegments(0);
	for (unsigned int i = 0; i < m_Segments.size(); ++i)
		delete m_Segments[i];
}

int SegmentList::TimeToClient(double time)
{
	int w = Width() - 2 * m_Gap;
	return m_Gap + round(w * (time - m_Timeline.GetTimeOffset()) / m_Timeline.GetTimePage());
}

double SegmentList::ClientToTime(int x)
{
	int w = Width() - 2 * m_Gap;
	return m_Timeline.GetTimeOffset() + m_Timeline.GetTimePage() * double(x - m_Gap) / w;
}

void SegmentList::SetSegments(SegmentSet * pSegments)
{
	if (m_pSegments == pSegments)
		return;
	if (m_pSegments != 0) {
		m_pSegments->GetDoc().Detach(this);
		m_pSegments->Detach(this);
	}
	m_pSegments = pSegments;
	if (m_pSegments != 0) {
		m_pSegments->Attach(this);
		m_pSegments->GetDoc().Attach(this);
		UpdateVPage();
		UpdateVRange();	
		SetVOffset(0);
		UpdateSegments();
		Invalidate();
	}
}

void SegmentList::UpdateSegments()
{
	int count = GetSegments()->IsEmpty() ? 0 : int(GetSegments()->GetSegmentCount());
	int i = 0;
	for (; i < count; ++i)
	{
		if (i > int(m_Segments.size()) - 1)
			m_Segments.push_back(new SegmentControl(* this));
		SegmentControl * pControl = m_Segments[i];
		pControl->SetSegmentIndex(i);
	}
	for (; i < int(m_Segments.size()); ++i)
	{
		SegmentControl * pControl = m_Segments[i];
		pControl->SetSegmentIndex(-1);
	}
}

void SegmentList::ProcessEvent(My::Event & e)
{
	if (e.Is<TimerEvent>()) {
		OnAutoScroll();
	} else
	if (e.Is<SegmentSet::Change>()) {
		UpdateVPage();
		UpdateVRange();	
		SetVOffset(0);
		UpdateSegments();
		Invalidate();
	} else
	if (e.Is<Timeline::TimeOffsetChange>()) {
		Invalidate();
	} else 
	if (e.Is<Timeline::TimeScaleChange>()) {
		Invalidate();
	} else 
	if (e.Is<Sequence::CurrentFrameChange>()) {
		Invalidate();
	} else 
	if (e.Is<Sequence::PreviewFrameChange>()) {
		Invalidate();
	} else 
	if (e.Is<Sequence::WorkAreaChange>()) {
		Invalidate();
	} else
	if (e.Is<Doc::SelectionChange>()) {
		Invalidate();
	} else
		Container::ProcessEvent(e);
}

void SegmentList::Paint(CDC * pDC, const CRect & rect)
{
	My::SaveObjects save(pDC);

	COLORREF cF = ::GetSysColor(COLOR_BTNFACE);
	COLORREF cS = ::GetSysColor(COLOR_BTNSHADOW);
	COLORREF cL = ::GetSysColor(COLOR_BTNHILIGHT);
	COLORREF cLF = My::MeanColor(cL, cF);
	COLORREF cFS = My::MeanColor(cF, cS);
	COLORREF cFFS = My::MeanColor(cF, cFS);
	COLORREF cLFF = My::MeanColor(cLF, cF);
	COLORREF cViolet = RGB(242, 242, 255);
	COLORREF cDViolet = RGB(227, 227, 255);
	COLORREF cDDViolet = RGB(212, 212, 255);

	int nCount = std::min(m_Segments.size(),
		GetSegments()->GetSegmentCount());
	int nMin = GetVOffset() / RowHeight;
	int nMax = (GetVOffset() + GetVPage()) / RowHeight;
	if (nMax > nCount - 1)
		nMax = nCount - 1;

	CRect rClient = ClientRect();
	CRect rWorkArea(rClient);
	CRect rCurrent(rClient);

	CRect rSegmentArea(rClient);
	rSegmentArea.top = RowHeight + DividerHeight;

	// Filling the ground

	CBrush bFace(cF);
	pDC->FillRect(&rClient, &bFace);

	if (!m_Timeline.IsEmpty()) {

		rWorkArea.left = TimeToClient(m_Timeline.GetWorkArea().first);
		rWorkArea.right = TimeToClient(m_Timeline.GetWorkArea().last);
		rWorkArea.IntersectRect(&rWorkArea, &rClient);

		CBrush bWorkArea(cLF);
		pDC->FillRect(&rWorkArea, &bWorkArea);

		rCurrent.left = TimeToClient(m_Timeline.GetSequence()->GetCurrentFrame());
		rCurrent.right = TimeToClient(m_Timeline.GetSequence()->GetCurrentFrame() + 1);

		CBrush bCurrent(cViolet);
		pDC->FillRect(&rCurrent, &bCurrent);

		for (int i = nMin; i <= nMax; ++i) {

			CRect rSegment(rClient);
			rSegment.top = rSegmentArea.top + 2 + i * RowHeight - GetVOffset();
			rSegment.bottom = rSegment.top + RowHeight;
			rSegment.IntersectRect(&rSegment, &rSegmentArea);

			CRect rSegmentWorkArea;
			rSegmentWorkArea.IntersectRect(&rSegment, &rWorkArea);

			CRect rSegmentCurrent;
			rSegmentCurrent.IntersectRect(&rSegment, &rCurrent);

			if (i == GetSegments()->GetActiveSegment()) {

				CBrush bGround(cFFS);
				pDC->FillRect(&rSegment, &bGround);

				CBrush bWorkArea(cLFF);
				pDC->FillRect(&rSegmentWorkArea, &bWorkArea);

				CBrush bCurrent(cDViolet);
				pDC->FillRect(&rSegmentCurrent, &bCurrent);

			}
		}
	}

	// Drawing lines

	CPen pShadow(PS_SOLID, 1, cS);
	pDC->SelectObject(&pShadow);

	pDC->MoveTo(rClient.left, rClient.top);
	pDC->LineTo(rClient.right, rClient.top);

	pDC->MoveTo(rClient.left, rClient.top + RowHeight - 1 + DividerHeight);
	pDC->LineTo(rClient.right, rClient.top + RowHeight - 1 + DividerHeight);

	CPen pLight(PS_SOLID, 1, cL);
	pDC->SelectObject(&pLight);

	pDC->MoveTo(rClient.left, rClient.top + RowHeight - 1);
	pDC->LineTo(rClient.right, rClient.top + RowHeight - 1);

	// Drawing controls

	{ 
		My::ClipRect clip(this, pDC, rSegmentArea);

		for (int i = nMin; i <= nMax; ++i) {
			PaintChild(pDC, rect, m_Segments[i]);
			int y = rSegmentArea.top + 2 + i * RowHeight - GetVOffset() + RowHeight - 1;
			pDC->MoveTo(rClient.left, y);
			pDC->LineTo(rClient.right, y);
		}
	}

	if (!m_Timeline.IsEmpty()) {

		CPen pRed(PS_SOLID, 1, RGB(255, 0, 0));

		pDC->SelectObject(&pRed);
		pDC->MoveTo(rCurrent.left, rCurrent.top);
		pDC->LineTo(rCurrent.left, rCurrent.bottom);

		if (m_Timeline.GetSequence()->GetCurrentFrame() != m_Timeline.GetSequence()->GetPreviewFrame())
		{
			rCurrent.left = rClient.left + TimeToClient(m_Timeline.GetSequence()->GetPreviewFrame());
			My::XorAlternateVertLine(pDC, 
				rCurrent.left, rCurrent.top, rCurrent.bottom, RGB(255, 255, 255));
		}
	}

	if (m_ShowInsertion) {

		My::ClipRect clip(this, pDC, rSegmentArea);

		m_InsertionY = GetInsertionY(m_Insertion);
		m_InsertionVisible = true;

		CPen pen(PS_SOLID, 4, RGB(0, 0, 0));
		pDC->SelectObject(&pen);

		int nROP = pDC->SetROP2(R2_NOTXORPEN);

		pDC->MoveTo(0, m_InsertionY);
		pDC->LineTo(Width(), m_InsertionY);

		pDC->SetROP2(nROP);
	}
}

void SegmentList::HandleLeftDown(unsigned int flags, const CPoint & p)
{
	if (m_Timeline.IsEmpty())
		return;
	GetSegments()->SetActiveSegment(-1);
	My::Container::HandleLeftDown(flags, p);
}

void SegmentList::UpdateAnimation()
{
	m_Animator.Enable(!m_Animated.empty());
	for (SegmentControlIndex i = m_Animated.begin(); i != m_Animated.end(); ++i)
		(* i)->Animate();
}

int SegmentList::GetInsertionY(int pos)
{
	return pos == -1 ? INT_MAX :		
		RowHeight + DividerHeight + 2 + pos * RowHeight - GetVOffset();
}

void SegmentList::ShowInsertion()
{
	if (m_InsertionVisible || !m_ShowInsertion)
		return;
	m_InsertionY = GetInsertionY(m_Insertion);
	m_InsertionVisible = DrawInsertion();
}

void SegmentList::HideInsertion()
{
	if (!m_InsertionVisible || !m_ShowInsertion)
		return;
	m_InsertionVisible = !DrawInsertion();
}

bool SegmentList::DrawInsertion()
{
	if (m_InsertionY == INT_MAX)
		return false;

	My::ClientDC dc(this);
	My::SaveObjects save(&dc);
	
	CRect rClient = ClientRect();
	CRect rSegmentArea(rClient);
	rSegmentArea.top = RowHeight + DividerHeight;
	My::ClipRect clip(this, &dc, rSegmentArea);

	CPen pen(PS_SOLID, 4, RGB(0, 0, 0));
	dc.SelectObject(&pen);

	int nROP = dc.SetROP2(R2_NOTXORPEN);

	dc.MoveTo(0, m_InsertionY);
	dc.LineTo(Width(), m_InsertionY);

	dc.SetROP2(nROP);

	return true;
}

void SegmentList::RedrawInsertion()
{
	My::ClientDC dc(this);
	My::SaveObjects save(&dc);
	
	CRect rClient = ClientRect();
	CRect rSegmentArea(rClient);
	rSegmentArea.top = RowHeight + DividerHeight;
	My::ClipRect clip(this, &dc, rSegmentArea);

	CPen pen(PS_SOLID, 4, RGB(0, 0, 0));
	dc.SelectObject(&pen);

	int nROP = dc.SetROP2(R2_NOTXORPEN);

	if (m_InsertionY != INT_MAX) {
		dc.MoveTo(0, m_InsertionY);
		dc.LineTo(Width(), m_InsertionY);
	}
	m_InsertionY = GetInsertionY(m_Insertion);
	if (m_InsertionY != INT_MAX) {
		dc.MoveTo(0, m_InsertionY);
		dc.LineTo(Width(), m_InsertionY);
	}
	dc.SetROP2(nROP);
}

void SegmentList::Insertion(int pos)
{
	if (pos < 0)
		pos = 0;
	if (pos > int(GetSegments()->GetSegmentCount()))
		pos = GetSegments()->GetSegmentCount();
	if (pos == m_InsertionSource || pos == m_InsertionSource + 1)
		pos = -1;
	if (m_Insertion != pos) {
		m_Insertion = pos;
		RedrawInsertion();
	}
}

void SegmentList::OnAutoScroll(bool mouseMove)
{
	if (m_VAutoScroll != 0)
	{
		SetVOffset(m_VOffset + m_VAutoScroll);

		if (mouseMove) {
		// Simulate mouse movement:
			CPoint p; ::GetCursorPos(&p);
			p = ScreenToClient(p);
			My::MouseMoveEvent e(0, p);
			ProcessEvent(e);
		}
	}
}

void SegmentList::SetVOffset(int offset)
{
	if (GetSegments()->IsEmpty())
		return;
	if (offset > m_VRange - m_VPage)
		offset = m_VRange - m_VPage;
	if (offset < 0)
		offset = 0;
	if (offset != m_VOffset)
	{
		m_VOffset = offset;
		UpdateVScrollBar();
		Invalidate();
	}
}

void SegmentList::UpdateVRange()
{
	if (GetSegments()->IsEmpty()) {
		m_VRange = 0;
	} else {
		m_VRange = 2 + (GetSegments()->GetSegmentCount() + 1) * RowHeight;
	}
	UpdateVOffset();
	UpdateVScrollBar();
}

void SegmentList::UpdateVPage()
{
	if (GetSegments()->IsEmpty()) {
		m_VPage = 0;
	} else {
		m_VPage = Height() - RowHeight - DividerHeight;
	}
	UpdateVOffset();
	UpdateVScrollBar();
}

void SegmentList::UpdateVScrollBar()
{
	SCROLLINFO info;
	info.cbSize = sizeof(SCROLLINFO);     
	info.fMask = SIF_ALL|SIF_DISABLENOSCROLL;     
	info.nMin = 0;     
	info.nMax = m_VRange; 
	info.nPage = m_VPage;
	info.nPos = m_VOffset;
	info.nTrackPos = m_VOffset;
	if (m_VRange == m_VPage)
		info.nPage++;
	m_VScrollBar.SetInfo(info);
}

void SegmentList::SetVAutoScroll(int d)
{
	m_VAutoScroll = d; 
	m_AutoScroll.Enable(d != 0);
}

void SegmentList::HandleResize(int sx, int sy)
{
	UpdateVPage();
}

} // My